001 /*
002 * Copyright 2004-2006 Stephen J. McConnell.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.metro.info;
020
021 import java.io.Serializable;
022 import java.beans.IntrospectionException;
023 import java.util.ArrayList;
024 import java.lang.reflect.Method;
025 import java.net.URI;
026 import java.net.URL;
027
028 import net.dpml.lang.AbstractDirective;
029
030 import net.dpml.state.State;
031 import net.dpml.state.StateDecoder;
032 import net.dpml.state.StateBuilderRuntimeException;
033
034 /**
035 * This class contains the meta information about a particular
036 * component type. It describes;
037 *
038 * <ul>
039 * <li>Human presentable meta data such as name, version, description etc
040 * useful when assembling a system.</li>
041 * <li>the context that this component requires</li>
042 * <li>the services that this component type is capable of providing</li>
043 * <li>the services that this component type requires to operate (and the
044 * names via which services are accessed)</li>
045 * <li>information about the component lifestyle</li>
046 * <li>component collection preferences</li>
047 * </ul>
048 *
049 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
050 * @version 1.0.1
051 */
052 public class Type extends AbstractDirective implements Serializable
053 {
054 static final long serialVersionUID = 1L;
055
056 private static final StateDecoder STATE_DECODER = new StateDecoder();
057
058 private final InfoDescriptor m_info;
059 private final CategoryDescriptor[] m_categories;
060 private final ContextDescriptor m_context;
061 private final ServiceDescriptor[] m_services;
062 private final State m_graph;
063
064 /**
065 * Creation of a new Type instance using a supplied component descriptor,
066 * logging, context, services, and part references.
067 *
068 * @param info information about the component type
069 * @param loggers a set of logger descriptors the declare the logging channels
070 * required by the type
071 * @param context a component context descriptor that declares the context type
072 * and context entry key and value classnames
073 * @param services a set of service descriptors that detail the service that
074 * this component type is capable of supplying
075 * @param graph the state graph
076 * @exception NullPointerException if the info, loggers, state, or context is null
077 */
078 public Type(
079 final InfoDescriptor info, final CategoryDescriptor[] loggers,
080 final ContextDescriptor context, final ServiceDescriptor[] services, final State graph )
081 throws NullPointerException
082 {
083 if( null == info )
084 {
085 throw new NullPointerException( "info" );
086 }
087 if( null == loggers )
088 {
089 throw new NullPointerException( "loggers" );
090 }
091 if( null == context )
092 {
093 throw new NullPointerException( "context" );
094 }
095 if( null == graph )
096 {
097 throw new NullPointerException( "graph" );
098 }
099 if( null == services )
100 {
101 m_services = new ServiceDescriptor[0];
102 }
103 else
104 {
105 m_services = services;
106 }
107
108 m_info = info;
109 m_categories = loggers;
110 m_context = context;
111 m_graph = graph;
112 }
113
114 /**
115 * Return the state graph for the component type.
116 * @return the state graph
117 */
118 public State getStateGraph()
119 {
120 return m_graph;
121 }
122
123 /**
124 * Return the info descriptor.
125 *
126 * @return the component info descriptor.
127 */
128 public InfoDescriptor getInfo()
129 {
130 return m_info;
131 }
132
133 /**
134 * Return the set of Logger that this Component will use.
135 *
136 * @return the set of Logger that this Component will use.
137 */
138 public CategoryDescriptor[] getCategoryDescriptors()
139 {
140 return m_categories;
141 }
142
143 /**
144 * Return TRUE if the logging categories includes a category with
145 * a matching name.
146 *
147 * @param name the logging category name
148 * @return TRUE if the logging category is declared.
149 */
150 public boolean isaCategory( String name )
151 {
152 CategoryDescriptor[] loggers = getCategoryDescriptors();
153 for( int i = 0; i < loggers.length; i++ )
154 {
155 CategoryDescriptor logger = loggers[ i ];
156 if( logger.getName().equals( name ) )
157 {
158 return true;
159 }
160 }
161 return false;
162 }
163
164 /**
165 * Return the ContextDescriptor for component.
166 *
167 * @return the ContextDescriptor for component.
168 */
169 public ContextDescriptor getContextDescriptor()
170 {
171 return m_context;
172 }
173
174 /**
175 * Get the set of service descriptors defining the set of services that
176 * the component type exports.
177 *
178 * @return the array of service descriptors
179 */
180 public ServiceDescriptor[] getServiceDescriptors()
181 {
182 return m_services;
183 }
184
185 /**
186 * Retrieve a service descriptor matching the supplied reference.
187 *
188 * @param reference a service descriptor to match against
189 * @return a matching service descriptor or null if no match found
190 */
191 public ServiceDescriptor getServiceDescriptor( final ServiceDescriptor reference )
192 {
193 for ( int i = 0; i < m_services.length; i++ )
194 {
195 final ServiceDescriptor service = m_services[i];
196 if ( service.matches( reference ) )
197 {
198 return service;
199 }
200 }
201 return null;
202 }
203
204 /**
205 * Retrieve a service descriptor matching the supplied classname.
206 *
207 * @param classname the service classname
208 * @return the matching service descriptor or null if it does not exist
209 */
210 public ServiceDescriptor getServiceDescriptor( final String classname )
211 {
212 for ( int i = 0; i < m_services.length; i++ )
213 {
214 final ServiceDescriptor service = m_services[i];
215 if ( service.getClassname().equals( classname ) )
216 {
217 return service;
218 }
219 }
220 return null;
221 }
222
223 /**
224 * Return a string representation of the type.
225 * @return the stringified type
226 */
227 public String toString()
228 {
229 return getInfo().toString();
230 }
231
232 /**
233 * Test is the supplied object is equal to this object.
234 * @param other the other object
235 * @return true if the object are equivalent
236 */
237 public boolean equals( Object other )
238 {
239 if( !super.equals( other ) )
240 {
241 return false;
242 }
243 if( !( other instanceof Type ) )
244 {
245 return false;
246 }
247 Type t = (Type) other;
248 if( !m_info.equals( t.m_info ) )
249 {
250 return false;
251 }
252 if( !m_context.equals( t.m_context ) )
253 {
254 return false;
255 }
256 if( !m_graph.equals( t.m_graph ) )
257 {
258 return false;
259 }
260 for( int i=0; i<m_categories.length; i++ )
261 {
262 if( !m_categories[i].equals( t.m_categories[i] ) )
263 {
264 return false;
265 }
266 }
267 for( int i=0; i<m_services.length; i++ )
268 {
269 if( !m_services[i].equals( t.m_services[i] ) )
270 {
271 return false;
272 }
273 }
274 return true;
275 }
276
277 /**
278 * Return the hashcode for the object.
279 * @return the hashcode value
280 */
281 public int hashCode()
282 {
283 int hash = super.hashCode();
284 hash ^= m_info.hashCode();
285 hash ^= m_context.hashCode();
286 hash ^= m_graph.hashCode();
287 for( int i = 0; i < m_services.length; i++ )
288 {
289 hash ^= m_services[i].hashCode();
290 hash = hash - 163611323;
291 }
292 for( int i = 0; i < m_categories.length; i++ )
293 {
294 hash ^= m_categories[i].hashCode();
295 hash = hash + 471312761;
296 }
297 return hash;
298 }
299
300 /**
301 * Utility operation to construct a default type given a supplied class.
302 * @param subject the component implementation class
303 * @return the type descriptor for the class
304 * @exception IntrospectionException if an introspection error occurs
305 */
306 public static Type createType( Class subject )
307 throws IntrospectionException
308 {
309 final InfoDescriptor info = new InfoDescriptor( null, subject.getName() );
310 final CategoryDescriptor[] loggers = new CategoryDescriptor[0];
311 final ContextDescriptor context = createContextDescriptor( subject );
312 final ServiceDescriptor[] services =
313 new ServiceDescriptor[]{
314 new ServiceDescriptor( subject.getName() )
315 };
316 State state = loadStateFromResource( subject );
317 return new Type( info, loggers, context, services, state );
318 }
319
320 private static ContextDescriptor createContextDescriptor( Class subject )
321 throws IntrospectionException
322 {
323 EntryDescriptor[] entries = createEntryDescriptors( subject );
324 return new ContextDescriptor( entries );
325 }
326
327 private static EntryDescriptor[] createEntryDescriptors( Class subject )
328 throws IntrospectionException
329 {
330 String classname = subject.getName();
331 Class[] classes = subject.getClasses();
332 Class param = locateClass( "$Context", classes );
333 if( null == param )
334 {
335 return new EntryDescriptor[0];
336 }
337 else
338 {
339 //
340 // For each method in the Context inner-interface we construct a
341 // descriptor that establishes the key, type, and required status.
342 //
343
344 Method[] methods = param.getMethods();
345 ArrayList list = new ArrayList();
346 for( int i=0; i<methods.length; i++ )
347 {
348 Method method = methods[i];
349 String name = method.getName();
350 if( name.startsWith( "get" ) )
351 {
352 EntryDescriptor descriptor =
353 createEntryDescriptor( method );
354 list.add( descriptor );
355 }
356 }
357 return (EntryDescriptor[]) list.toArray( new EntryDescriptor[0] );
358 }
359 }
360
361 /**
362 * Creation of a new parameter descriptor using a supplied method.
363 * The method is the method used by the component implementation to get the parameter
364 * instance.
365 */
366 private static EntryDescriptor createEntryDescriptor( Method method )
367 throws IntrospectionException
368 {
369 validateMethodName( method );
370 validateNoExceptions( method );
371
372 String key = EntryDescriptor.getEntryKey( method );
373
374 Class returnType = method.getReturnType();
375 if( method.getParameterTypes().length == 0 )
376 {
377 //
378 // required context entry
379 //
380
381 validateNonNullReturnType( method );
382 String type = returnType.getName();
383 return new EntryDescriptor( key, type, EntryDescriptor.REQUIRED );
384 }
385 else if( method.getParameterTypes().length == 1 )
386 {
387 Class[] params = method.getParameterTypes();
388 Class param = params[0];
389 if( returnType.isAssignableFrom( param ) )
390 {
391 String type = param.getName();
392 return new EntryDescriptor( key, type, EntryDescriptor.OPTIONAL );
393 }
394 else
395 {
396 final String error =
397 "Context entry assessor declares an optional default parameter class ["
398 + param.getName()
399 + "] which is not assignable to the return type ["
400 + returnType.getName()
401 + "]";
402 throw new IntrospectionException( error );
403 }
404 }
405 else
406 {
407 final String error =
408 "Unable to establish a required or optional context entry method pattern on ["
409 + method.getName()
410 + "]";
411 throw new IntrospectionException( error );
412 }
413 }
414
415 private static void validateMethodName( Method method )
416 throws IntrospectionException
417 {
418 if( !method.getName().startsWith( "get" ) )
419 {
420 final String error =
421 "Method ["
422 + method.getName()
423 + "] does not start with 'get'.";
424 throw new IntrospectionException( error );
425 }
426 }
427
428 private static void validateNoExceptions( Method method )
429 throws IntrospectionException
430 {
431 Class[] exceptionTypes = method.getExceptionTypes();
432 if( exceptionTypes.length > 0 )
433 {
434 final String error =
435 "Method ["
436 + method.getName()
437 + "] declares one or more exceptions.";
438 throw new IntrospectionException( error );
439 }
440 }
441
442 private static void validateNonNullReturnType( Method method )
443 throws IntrospectionException
444 {
445 Class returnType = method.getReturnType();
446 if( Void.TYPE.equals( returnType ) )
447 {
448 final String error =
449 "Method ["
450 + method.getName()
451 + "] does not declare a return type.";
452 throw new IntrospectionException( error );
453 }
454 }
455
456 private static Class locateClass( String postfix, Class[] classes )
457 {
458 for( int i=0; i<classes.length; i++ )
459 {
460 Class inner = classes[i];
461 String name = inner.getName();
462 if( name.endsWith( postfix ) )
463 {
464 return inner;
465 }
466 }
467 return null;
468 }
469
470 private static State loadStateFromResource( Class subject )
471 {
472 String resource = subject.getName().replace( '.', '/' ) + ".xgraph";
473 try
474 {
475 URL url = subject.getClassLoader().getResource( resource );
476 if( null == url )
477 {
478 return State.NULL_STATE;
479 }
480 else
481 {
482 URI uri = new URI( url.toString() );
483 return STATE_DECODER.loadState( uri );
484 }
485 }
486 catch( Throwable e )
487 {
488 final String error =
489 "Internal error while attempting to load component state graph resource ["
490 + resource
491 + "].";
492 throw new StateBuilderRuntimeException( error, e );
493 }
494 }
495 }